{.experimental: "callOperator", experimental: "implicitDeref".}

import sugar
import bigints, zero_functional

export bigints

type
  JellyValKind* = enum
    jvInt
    jvFloat
    jvStr
    jvList
  JellyVal* = object
    case kind*: JellyValKind
    of jvInt:
      intVal*: BigInt
    of jvFloat:
      floatVal*: float64
    of jvStr:
      strVal*: string
    of jvList:
      list*: ref seq[JellyVal]
  JellyArity* = enum
    jaNilad
    jaMonad
    jaDyad
  JellyLink* = object
    case arity*: JellyArity
    of jaNilad:
      nilad: () -> JellyVal
    of jaMonad:
      monad: JellyVal -> JellyVal
    of jaDyad:
      dyad: (JellyVal, JellyVal) -> JellyVal

func jelly*(x: JellyVal): JellyVal =
  x

func jelly*(x: BigInt): JellyVal =
  JellyVal(kind: jvInt, intVal: x)

func jelly*(x: SomeInteger): JellyVal =
  JellyVal(kind: jvInt, intVal: x.initBigInt)

func jelly*(x: SomeFloat): JellyVal =
  JellyVal(kind: jvFloat, floatVal: x)

func jelly*(x: string): JellyVal =
  JellyVal(kind: jvStr, strVal: x)

func jelly*[T](x: seq[T]): JellyVal =
  result = JellyVal(kind: jvList)
  new result.list
  result.list[] = x-->map(it.jelly)

func `==`*(x: JellyVal, y: JellyVal): bool =
  if x.kind != y.kind:
    return false
  case x.kind
  of jvInt:
    return x.intVal == y.intVal
  of jvFloat:
    # TODO: better comparison of floats
    return abs(x.floatVal - y.floatVal) < 1e-8
  of jvStr:
    return x.strVal == y.strVal
  of jvList:
    return x.list[] == y.list[]

func toJellyLink*(f: () -> JellyVal): JellyLink =
  JellyLink(arity: jaNilad, nilad: f)

func toJellyLink*(f: JellyVal -> JellyVal): JellyLink =
  JellyLink(arity: jaMonad, monad: f)

func toJellyLink*(f: (JellyVal, JellyVal) -> JellyVal): JellyLink =
  JellyLink(arity: jaDyad, dyad: f)

proc `()`*(link: JellyLink): JellyVal =
  link.nilad()

proc `()`*(link: JellyLink; z: JellyVal): JellyVal =
  link.monad(z)

proc `()`*(link: JellyLink; x, y: JellyVal): JellyVal =
  link.dyad(x, y)
